{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "## homework_function_call.ipynb 本文内容主要是完成以下两个homework\n",
    "## Homework 1\n",
    "**使用 openai.ChatCompletion.create 方法代替 chat_completion_request 实现本 notebook 所有功能.**\n",
    "## Homework 2\n",
    "**使用类似搜索引擎API，实现完整可执行的天气查询应用**\n",
    "* 本文采用的天气查询引擎API是：https://www.apispace.com/\n",
    "* 需要注册登陆该网站并获取apikey，截止：2023-07-28日，该网站每一类接口，新用户有200次免费使用权限\n",
    "* #### 注意：APISpaceApikey 请务必添加读者自己的获取天气的apikey"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "outputs": [],
   "source": [
    "import os\n",
    "import openai\n",
    "from tenacity import retry, wait_random_exponential, stop_after_attempt\n",
    "from termcolor import colored\n",
    "\n",
    "GPT_MODEL = \"gpt-3.5-turbo\"\n",
    "openai.api_key = os.getenv(\"OPENAI_API_KEY\")\n",
    "APISpaceApikey = \"xxxxx\"  # 获取天气的apikey，APISpaceApikey 请务必添加读者自己的获取天气的apikey"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:30:55.548882Z",
     "start_time": "2023-07-28T09:30:55.203073Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br/>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [],
   "source": [
    "# 1.定义一个函数chat_completion_api，通过 OpenAI Python 库调用 Chat Completions API\n",
    "@retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3))\n",
    "def chat_completion_api(messages, functions=None, function_call=None, model=GPT_MODEL):\n",
    "    \"\"\"\n",
    "    :param messages:\n",
    "    :param functions:\n",
    "    :param function_call: \"none\" is the default when no functions are present. \"auto\" is the default if functions are present.\n",
    "    :param model:\n",
    "    :return:\n",
    "    \"\"\"\n",
    "    try:\n",
    "        if functions:\n",
    "            if function_call:\n",
    "                response = openai.ChatCompletion.create(\n",
    "                    model=model,\n",
    "                    messages=messages,\n",
    "                    functions=functions,\n",
    "                    function_call=function_call\n",
    "                )\n",
    "            else:\n",
    "                response = openai.ChatCompletion.create(\n",
    "                    model=model,\n",
    "                    messages=messages,\n",
    "                    functions=functions,\n",
    "                    # function_call=\"auto\"\n",
    "                )\n",
    "        else:\n",
    "            response = openai.ChatCompletion.create(\n",
    "                model=model,\n",
    "                messages=messages\n",
    "            )\n",
    "        # 解析返回的数据，获取助手的回复消息\n",
    "        assistant_message = response[\"choices\"][0][\"message\"]\n",
    "        return assistant_message\n",
    "    except Exception as e:\n",
    "        print(\"Unable to generate ChatCompletion response\")\n",
    "        print(f\"Exception: {e}\")\n",
    "        return e"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:31:08.004435Z",
     "start_time": "2023-07-28T09:31:08.000012Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "outputs": [],
   "source": [
    "# 定义一个函数pretty_print_conversation，用于打印消息对话内容\n",
    "def pretty_print_conversation(messages):\n",
    "    # 为不同角色设置不同的颜色\n",
    "    role_to_color = {\n",
    "        \"system\": \"red\",\n",
    "        \"user\": \"green\",\n",
    "        \"assistant\": \"blue\",\n",
    "        \"function\": \"magenta\",\n",
    "    }\n",
    "\n",
    "    # 遍历消息列表\n",
    "    for message in messages:\n",
    "\n",
    "        # 如果消息的角色是\"system\"，则用红色打印“content”\n",
    "        if message[\"role\"] == \"system\":\n",
    "            print(colored(f\"system: {message['content']}\\n\", role_to_color[message[\"role\"]]))\n",
    "\n",
    "        # 如果消息的角色是\"user\"，则用绿色打印“content”\n",
    "        elif message[\"role\"] == \"user\":\n",
    "            print(colored(f\"user: {message['content']}\\n\", role_to_color[message[\"role\"]]))\n",
    "\n",
    "        # 如果消息的角色是\"assistant\"，并且消息中包含\"function_call\"，则用蓝色打印\"function_call\"\n",
    "        elif message[\"role\"] == \"assistant\" and message.get(\"function_call\"):\n",
    "            print(colored(f\"assistant[function_call]: {message['function_call']}\\n\", role_to_color[message[\"role\"]]))\n",
    "\n",
    "        # 如果消息的角色是\"assistant\"，但是消息中不包含\"function_call\"，则用蓝色打印“content”\n",
    "        elif message[\"role\"] == \"assistant\" and not message.get(\"function_call\"):\n",
    "            print(colored(f\"assistant[content]: {message['content']}\\n\", role_to_color[message[\"role\"]]))\n",
    "\n",
    "        # 如果消息的角色是\"function\"，则用品红色打印“function”\n",
    "        elif message[\"role\"] == \"function\":\n",
    "            print(colored(f\"function ({message['name']}): {message['content']}\\n\", role_to_color[message[\"role\"]]))"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:31:30.669604Z",
     "start_time": "2023-07-28T09:31:30.664034Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br/>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [],
   "source": [
    "# 定义一个名为functions的列表，其中包含两个字典，这两个字典分别定义了两个功能的相关参数\n",
    "# 第一个字典定义了一个名为\"get_current_weather\"的function\n",
    "functions = [\n",
    "    {\n",
    "        \"name\": \"get_current_weather\",  # function的名称\n",
    "        \"description\": \"Get the current weather\",  # function的描述\n",
    "        \"parameters\": {  # 定义该function需要的参数\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {  # 参数的属性\n",
    "                \"location\": {  # 地点参数\n",
    "                    \"type\": \"string\",  # 参数类型为字符串\n",
    "                    \"description\": \"The city and state, e.g. San Francisco, CA\",  # 参数的描述\n",
    "                },\n",
    "                \"format\": {  # 温度单位参数\n",
    "                    \"type\": \"string\",  # 参数类型为字符串\n",
    "                    \"enum\": [\"celsius\", \"fahrenheit\"],  # 参数的取值范围\n",
    "                    \"description\": \"The temperature unit to use. Infer this from the users location.\",  # 参数的描述\n",
    "                },\n",
    "            },\n",
    "            \"required\": [\"location\", \"format\"],  # 该functions需要的必要参数\n",
    "        },\n",
    "    },\n",
    "    # 第二个字典定义了一个名为\"get_n_day_weather_forecast\"的function\n",
    "    {\n",
    "        \"name\": \"get_n_day_weather_forecast\",  # function的名称\n",
    "        \"description\": \"Get an N-day weather forecast\",  # function的描述\n",
    "        \"parameters\": {  # 定义该function需要的参数\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {  # 参数的属性\n",
    "                \"location\": {  # 地点参数\n",
    "                    \"type\": \"string\",  # 参数类型为字符串\n",
    "                    \"description\": \"The city and state, e.g. San Francisco, CA\",  # 参数的描述\n",
    "                },\n",
    "                \"format\": {  # 温度单位参数\n",
    "                    \"type\": \"string\",  # 参数类型为字符串\n",
    "                    \"enum\": [\"celsius\", \"fahrenheit\"],  # 参数的取值范围\n",
    "                    \"description\": \"The temperature unit to use. Infer this from the users location.\",  # 参数的描述\n",
    "                },\n",
    "                \"num_days\": {  # 预测天数参数\n",
    "                    \"type\": \"integer\",  # 参数类型为整数\n",
    "                    \"description\": \"The number of days to forecast\",  # 参数的描述\n",
    "                }\n",
    "            },\n",
    "            \"required\": [\"location\", \"format\", \"num_days\"]  # 该function需要的必要参数\n",
    "        },\n",
    "    },\n",
    "]\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:31:34.470476Z",
     "start_time": "2023-07-28T09:31:34.464378Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "outputs": [],
   "source": [
    "\n",
    "import json\n",
    "import requests\n",
    "\n",
    "\n",
    "class WeatherApi(object):\n",
    "    \"\"\"这是一个对接天气预报的api的类\n",
    "    web url：https://www.apispace.com/\n",
    "    需要注册登陆该网站并获取apikey，截止：2023-07-28日，该网站每一类接口，新用户有200次免费使用权限\n",
    "    包括3个方法:，\n",
    "    1、获取城市代码：get_area_code()\n",
    "    2、获取当前天气信息：get_current_weather()\n",
    "    3、获取最近天气信息：get_n_day_weather_forecast()\n",
    "     \"\"\"\n",
    "    headers = {\n",
    "        \"X-APISpace-Token\": APISpaceApikey, # 添加自己的apikey\n",
    "        \"Authorization-Type\": \"apikey\"\n",
    "    }\n",
    "    base_url = \"https://eolink.o.apispace.com/456456/\"\n",
    "\n",
    "    def get_area_code(self, city, area):\n",
    "        \"\"\"\n",
    "        :param city: eg: 北京\n",
    "        :param area: eg: 中国\n",
    "        :return: 城市代码 eg: 101010100\n",
    "        \"\"\"\n",
    "        url = self.base_url + \"function/v001/city\"\n",
    "\n",
    "        payload = {\"location\": city, \"items\": \"1\", \"area\": area, \"language\": \"\", \"withTz\": \"\", \"withPoi\": \"false\"}\n",
    "        response = requests.request(\"GET\", url, params=payload, headers=self.headers)\n",
    "        # print(response.text)\n",
    "        return response.json()[\"areaList\"][0][\"areacode\"]\n",
    "\n",
    "    def get_current_weather(self, areacode):\n",
    "        \"\"\"\n",
    "        :param areacode: 城市代码 eg: 101010100\n",
    "        :return: str, {}\n",
    "        \"\"\"\n",
    "        url = self.base_url + \"weather/v001/now\"\n",
    "        payload = {\"areacode\": areacode}\n",
    "        response = requests.request(\"GET\", url, params=payload, headers=self.headers)\n",
    "        # print(response.text)\n",
    "        return response.text\n",
    "\n",
    "    def get_n_day_weather_forecast(self, areacode, num_days):\n",
    "        \"\"\"\n",
    "        :param areacode: 城市代码 eg: 101010100\n",
    "        :param num_days: eg: 2\n",
    "        :return: str, {}\n",
    "        \"\"\"\n",
    "        url = self.base_url + \"weather/v001/day\"\n",
    "        payload = {\"days\": num_days, \"areacode\": areacode}\n",
    "        response = requests.request(\"GET\", url, params=payload, headers=self.headers)\n",
    "        # print(response.text)\n",
    "        return response.text\n",
    "\n",
    "\n",
    "def execute_function_call(message):\n",
    "    \"\"\"执行函数调用\"\"\"\n",
    "    w_api = WeatherApi()\n",
    "    if message[\"function_call\"][\"name\"] == \"get_current_weather\":\n",
    "        arguments = json.loads(message[\"function_call\"][\"arguments\"])\n",
    "        city, area = str(arguments.get(\"location\")).split(', ')\n",
    "        areacode = w_api.get_area_code(city, area)\n",
    "        results = w_api.get_current_weather(areacode)\n",
    "    elif message[\"function_call\"][\"name\"] == \"get_n_day_weather_forecast\":\n",
    "        arguments = json.loads(message[\"function_call\"][\"arguments\"])\n",
    "        city, area = str(arguments.get(\"location\")).split(', ')\n",
    "        num_days = arguments.get(\"num_days\")\n",
    "        areacode = w_api.get_area_code(city, area)\n",
    "        results = w_api.get_n_day_weather_forecast(areacode, num_days)\n",
    "    else:\n",
    "        results = f\"Error: function {message['function_call']['name']} does not exist\"\n",
    "    return results  # 返回结果"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:31:38.736448Z",
     "start_time": "2023-07-28T09:31:38.733858Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br/>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: What's the weather like today\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure! Could you please let me know your current location?\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 定义一个空列表messages，用于存储聊天的内容\n",
    "messages = []\n",
    "\n",
    "# 使用append方法向messages列表添加一条系统角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"system\",  # 消息的角色是\"system\"\n",
    "    \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"\n",
    "    # 消息的内容\n",
    "})\n",
    "\n",
    "# 向messages列表添加一条用户角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 消息的角色是\"user\"\n",
    "    \"content\": \"What's the weather like today\"  # 用户询问今天的天气情况\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_api函数发起一个请求，传入messages和functions作为参数\n",
    "assistant_message = chat_completion_api(messages, functions=functions)\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:31:58.084684Z",
     "start_time": "2023-07-28T09:31:52.345506Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: What's the weather like today\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure! Could you please let me know your current location?\n",
      "\u001B[0m\n",
      "\u001B[32muser: I'm in Beijing, China.\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"get_current_weather\",\n",
      "  \"arguments\": \"{\\n  \\\"location\\\": \\\"Beijing, China\\\",\\n  \\\"format\\\": \\\"celsius\\\"\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (get_current_weather): {\"status\":0,\"result\":{\"location\":{\"areacode\":\"101010100\",\"name\":\"北京\",\"country\":\"中国\",\"path\":\"北京,北京市,北京市,中国\"},\"realtime\":{\"text\":\"阴\",\"code\":\"02\",\"temp\":29.4,\"feels_like\":31,\"rh\":58,\"wind_class\":\"1级\",\"wind_speed\":1.4,\"wind_dir\":\"东风\",\"wind_angle\":75,\"prec\":0.0,\"prec_time\":\"2023-07-28 17:00:00\",\"clouds\":95,\"vis\":27400,\"pressure\":1004,\"dew\":20,\"uv\":2,\"weight\":4,\"brief\":\"很闷热\",\"detail\":\"无风狂出汗，心静也不凉\"},\"last_update\":\"2023-07-28 17:31\"}}\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 向messages列表添加一条用户角色的消息，用户告知他们在苏格兰的格拉斯哥\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 消息的角色是\"user\"\n",
    "    \"content\": \"I'm in Beijing, China.\"  # 用户的消息内容\n",
    "})\n",
    "\n",
    "# 再次使用定义的chat_completion_api函数发起一个请求，传入更新后的messages和functions作为参数\n",
    "assistant_message = chat_completion_api(messages, functions=functions)\n",
    "# 将助手的新的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "if assistant_message.get(\"function_call\"):\n",
    "    results = execute_function_call(assistant_message)\n",
    "    messages.append({\"role\": \"function\", \"name\": assistant_message[\"function_call\"][\"name\"], \"content\": results})\n",
    "\n",
    "# print(assistant_message)\n",
    "\n",
    "\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:32:12.530162Z",
     "start_time": "2023-07-28T09:32:03.247082Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br/>\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: what is the weather going to be like in Shanghai, China over the next x days\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure, I can help you with that. Please provide the number of days you would like to forecast.\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 初始化一个空的messages列表\n",
    "messages = []\n",
    "\n",
    "# 向messages列表添加一条系统角色的消息，要求不做关于函数参数值的假设，如果用户的请求模糊，应该寻求澄清\n",
    "messages.append({\n",
    "    \"role\": \"system\",  # 消息的角色是\"system\"\n",
    "    \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"\n",
    "})\n",
    "\n",
    "# 向messages列表添加一条用户角色的消息，用户询问在未来x天内苏格兰格拉斯哥的天气情况\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 消息的角色是\"user\"\n",
    "    \"content\": \"what is the weather going to be like in Shanghai, China over the next x days\"\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_api函数发起一个请求，传入messages和functions作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions\n",
    ")\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 打印助手的回复消息\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:32:25.989040Z",
     "start_time": "2023-07-28T09:32:24.058688Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: what is the weather going to be like in Shanghai, China over the next x days\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure, I can help you with that. Please provide the number of days you would like to forecast.\n",
      "\u001B[0m\n",
      "\u001B[32muser: 2 days\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"get_n_day_weather_forecast\",\n",
      "  \"arguments\": \"{\\n  \\\"location\\\": \\\"Shanghai, China\\\",\\n  \\\"format\\\": \\\"celsius\\\",\\n  \\\"num_days\\\": 2\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (get_n_day_weather_forecast): {\"status\":0,\"result\":{\"location\":{\"areacode\":\"101020100\",\"name\":\"上海\",\"country\":\"中国\",\"path\":\"上海,上海市,上海市,中国\"},\"daily_fcsts\":[{\"text_day\":\"中雨\",\"code_day\":\"08\",\"text_night\":\"中雨\",\"code_night\":\"08\",\"high\":30,\"low\":25,\"wc_day\":\"4~5级\",\"wd_day\":\"东南风\",\"wc_night\":\"4~5级\",\"wd_night\":\"东南风\",\"wa_day\":118,\"wa_night\":118,\"ws_day\":7.2,\"ws_night\":7.2,\"pop\":80,\"pressure\":1005,\"maxrh\":90,\"minrh\":84,\"vis\":7000,\"clouds_day\":100,\"clouds_night\":100,\"uv\":2,\"date\":\"2023-07-28\",\"week\":\"星期五\"},{\"text_day\":\"小雨\",\"code_day\":\"07\",\"text_night\":\"小雨\",\"code_night\":\"07\",\"high\":31,\"low\":28,\"wc_day\":\"4~5级\",\"wd_day\":\"东南风\",\"wc_night\":\"4~5级\",\"wd_night\":\"东南风\",\"wa_day\":118,\"wa_night\":118,\"ws_day\":7.2,\"ws_night\":7.2,\"pop\":80,\"pressure\":1004,\"maxrh\":91,\"minrh\":85,\"vis\":7000,\"clouds_day\":100,\"clouds_night\":100,\"uv\":2,\"date\":\"2023-07-29\",\"week\":\"星期六\"}],\"last_update\":\"2023-07-28 15:40\"}}\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 向messages列表添加一条用户角色的消息，用户指定接下来的天数为5天\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 消息的角色是\"user\"\n",
    "    \"content\": \"5 days\"\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_request函数发起一个请求，传入messages和functions作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions\n",
    ")\n",
    "\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "if assistant_message.get(\"function_call\"):\n",
    "    results = execute_function_call(assistant_message)\n",
    "    messages.append({\"role\": \"function\", \"name\": assistant_message[\"function_call\"][\"name\"], \"content\": results})\n",
    "\n",
    "# 打印助手的回复消息\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:32:46.686743Z",
     "start_time": "2023-07-28T09:32:38.285703Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: what is the weather going to be like in Shanghai, China over the next x days\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure, I can help you with that. Please provide the number of days you would like to forecast.\n",
      "\u001B[0m\n",
      "\u001B[32muser: 2 days\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"get_n_day_weather_forecast\",\n",
      "  \"arguments\": \"{\\n  \\\"location\\\": \\\"Shanghai, China\\\",\\n  \\\"format\\\": \\\"celsius\\\",\\n  \\\"num_days\\\": 2\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (get_n_day_weather_forecast): {\"status\":0,\"result\":{\"location\":{\"areacode\":\"101020100\",\"name\":\"上海\",\"country\":\"中国\",\"path\":\"上海,上海市,上海市,中国\"},\"daily_fcsts\":[{\"text_day\":\"中雨\",\"code_day\":\"08\",\"text_night\":\"中雨\",\"code_night\":\"08\",\"high\":30,\"low\":25,\"wc_day\":\"4~5级\",\"wd_day\":\"东南风\",\"wc_night\":\"4~5级\",\"wd_night\":\"东南风\",\"wa_day\":118,\"wa_night\":118,\"ws_day\":7.2,\"ws_night\":7.2,\"pop\":80,\"pressure\":1005,\"maxrh\":90,\"minrh\":84,\"vis\":7000,\"clouds_day\":100,\"clouds_night\":100,\"uv\":2,\"date\":\"2023-07-28\",\"week\":\"星期五\"},{\"text_day\":\"小雨\",\"code_day\":\"07\",\"text_night\":\"小雨\",\"code_night\":\"07\",\"high\":31,\"low\":28,\"wc_day\":\"4~5级\",\"wd_day\":\"东南风\",\"wc_night\":\"4~5级\",\"wd_night\":\"东南风\",\"wa_day\":118,\"wa_night\":118,\"ws_day\":7.2,\"ws_night\":7.2,\"pop\":80,\"pressure\":1004,\"maxrh\":91,\"minrh\":85,\"vis\":7000,\"clouds_day\":100,\"clouds_night\":100,\"uv\":2,\"date\":\"2023-07-29\",\"week\":\"星期六\"}],\"last_update\":\"2023-07-28 15:40\"}}\n",
      "\u001B[0m\n",
      "\u001B[32muser: can you help me sum up the weather for the next few days?\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: Sure! Here is the weather forecast for the next 2 days in Shanghai, China:\n",
      "\n",
      "- Day 1 (July 28, 2023): Expect moderate rain with a high of 30°C and a low of 25°C. The wind will be blowing from the southeast at a speed of 4-5 m/s.\n",
      "\n",
      "- Day 2 (July 29, 2023): Expect light rain with a high of 31°C and a low of 28°C. The wind will be blowing from the southeast at a speed of 4-5 m/s.\n",
      "\n",
      "Please note that weather conditions can change, so it's always a good idea to stay updated with the latest forecast.\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "\n",
    "messages.append({\"role\": \"user\", \"content\": \"can you help me sum up the weather for the next few days?\"})\n",
    "# 使用定义的chat_completion_request函数发起一个请求，传入messages和functions作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions\n",
    ")\n",
    "\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "pretty_print_conversation(messages)\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:33:03.160646Z",
     "start_time": "2023-07-28T09:32:51.698492Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br />\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: Give me a weather report for San Diego, USA.\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"get_n_day_weather_forecast\",\n",
      "  \"arguments\": \"{\\n  \\\"location\\\": \\\"San Diego, USA\\\",\\n  \\\"format\\\": \\\"celsius\\\",\\n  \\\"num_days\\\": 1\\n}\"\n",
      "}\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 在这个代码单元中，我们强制GPT 模型使用get_n_day_weather_forecast函数\n",
    "messages = []  # 创建一个空的消息列表\n",
    "\n",
    "# 添加系统角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"system\",  # 角色为系统\n",
    "    \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"\n",
    "})\n",
    "\n",
    "# 添加用户角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 角色为用户\n",
    "    \"content\": \"Give me a weather report for San Diego, USA.\"\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_api函数发起一个请求，传入messages、functions以及特定的function_call作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions, function_call={\"name\": \"get_n_day_weather_forecast\"}\n",
    ")\n",
    "\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 打印助手的回复消息\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:33:14.042481Z",
     "start_time": "2023-07-28T09:33:10.764950Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "<br/>"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: Give me a weather report for San Diego, USA.\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"get_current_weather\",\n",
      "  \"arguments\": \"{\\n  \\\"location\\\": \\\"San Diego, USA\\\",\\n  \\\"format\\\": \\\"celsius\\\"\\n}\"\n",
      "}\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 如果我们不强制GPT 模型使用 get_n_day_weather_forecast，它可能不会使用\n",
    "messages = []  # 创建一个空的消息列表\n",
    "\n",
    "# 添加系统角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"system\",  # 角色为系统\n",
    "    \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"\n",
    "})\n",
    "\n",
    "# 添加用户角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 角色为用户\n",
    "    \"content\": \"Give me a weather report for San Diego, USA.\"\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_api函数发起一个请求，传入messages和functions作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions\n",
    ")\n",
    "\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 打印助手的回复消息\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:33:37.117676Z",
     "start_time": "2023-07-28T09:33:34.896834Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\n",
      "\u001B[0m\n",
      "\u001B[32muser: Give me the current weather (use Celcius) for Toronto, Canada.\n",
      "\u001B[0m\n",
      "\u001B[34massistant[content]: {\n",
      "  \"location\": \"Toronto, Canada\",\n",
      "  \"format\": \"celsius\"\n",
      "}\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 创建另一个空的消息列表\n",
    "messages = []\n",
    "\n",
    "# 添加系统角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"system\",  # 角色为系统\n",
    "    \"content\": \"Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous.\"\n",
    "})\n",
    "\n",
    "# 添加用户角色的消息\n",
    "messages.append({\n",
    "    \"role\": \"user\",  # 角色为用户\n",
    "    \"content\": \"Give me the current weather (use Celcius) for Toronto, Canada.\"\n",
    "})\n",
    "\n",
    "# 使用定义的chat_completion_api函数发起一个请求，传入messages、functions和function_call作为参数\n",
    "assistant_message = chat_completion_api(\n",
    "    messages, functions=functions, function_call=\"none\"\n",
    ")\n",
    "\n",
    "# 将助手的回复消息添加到messages列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 打印助手的回复消息\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:33:55.717503Z",
     "start_time": "2023-07-28T09:33:53.173450Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 使用functions 进行ckinook数据查询\n"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opened database successfully\n"
     ]
    }
   ],
   "source": [
    "import sqlite3\n",
    "\n",
    "conn = sqlite3.connect(\"data/chinook.db\")\n",
    "print(\"Opened database successfully\")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:34:35.244027Z",
     "start_time": "2023-07-28T09:34:35.238081Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "outputs": [],
   "source": [
    "def get_table_names(conn):\n",
    "    \"\"\"返回一个包含所有表名的列表\"\"\"\n",
    "    table_names = []  # 创建一个空的表名列表\n",
    "    # 执行SQL查询，获取数据库中所有表的名字\n",
    "    tables = conn.execute(\"SELECT name FROM sqlite_master WHERE type='table';\")\n",
    "    # 遍历查询结果，并将每个表名添加到列表中\n",
    "    for table in tables.fetchall():\n",
    "        table_names.append(table[0])\n",
    "    return table_names  # 返回表名列表\n",
    "\n",
    "\n",
    "def get_column_names(conn, table_name):\n",
    "    \"\"\"返回一个给定表的所有列名的列表\"\"\"\n",
    "    column_names = []  # 创建一个空的列名列表\n",
    "    # 执行SQL查询，获取表的所有列的信息\n",
    "    columns = conn.execute(f\"PRAGMA table_info('{table_name}');\").fetchall()\n",
    "    # 遍历查询结果，并将每个列名添加到列表中\n",
    "    for col in columns:\n",
    "        column_names.append(col[1])\n",
    "    return column_names  # 返回列名列表\n",
    "\n",
    "\n",
    "def get_database_info(conn):\n",
    "    \"\"\"返回一个字典列表，每个字典包含一个表的名字和列信息\"\"\"\n",
    "    table_dicts = []  # 创建一个空的字典列表\n",
    "    # 遍历数据库中的所有表\n",
    "    for table_name in get_table_names(conn):\n",
    "        columns_names = get_column_names(conn, table_name)  # 获取当前表的所有列名\n",
    "        # 将表名和列名信息作为一个字典添加到列表中\n",
    "        table_dicts.append({\"table_name\": table_name, \"column_names\": columns_names})\n",
    "    return table_dicts  # 返回字典列表\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:34:37.146396Z",
     "start_time": "2023-07-28T09:34:37.141422Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "outputs": [],
   "source": [
    "# 获取数据库信息，并存储为字典列表\n",
    "database_schema_dict = get_database_info(conn)\n",
    "\n",
    "# 将数据库信息转换为字符串格式，方便后续使用\n",
    "database_schema_string = \"\\n\".join(\n",
    "    [\n",
    "        f\"Table: {table['table_name']}\\nColumns: {', '.join(table['column_names'])}\"\n",
    "        for table in database_schema_dict\n",
    "    ]\n",
    ")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:34:45.036157Z",
     "start_time": "2023-07-28T09:34:45.027014Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "outputs": [],
   "source": [
    "# 定义一个功能列表，其中包含一个功能字典，该字典定义了一个名为\"ask_database\"的功能，用于回答用户关于音乐的问题\n",
    "functions = [\n",
    "    {\n",
    "        \"name\": \"ask_database\",\n",
    "        \"description\": \"Use this function to answer user questions about music. Output should be a fully formed SQL query.\",\n",
    "        \"parameters\": {\n",
    "            \"type\": \"object\",\n",
    "            \"properties\": {\n",
    "                \"query\": {\n",
    "                    \"type\": \"string\",\n",
    "                    \"description\": f\"\"\"\n",
    "                            SQL query extracting info to answer the user's question.\n",
    "                            SQL should be written using this database schema:\n",
    "                            {database_schema_string}\n",
    "                            The query should be returned in plain text, not in JSON.\n",
    "                            \"\"\",\n",
    "                }\n",
    "            },\n",
    "            \"required\": [\"query\"],\n",
    "        },\n",
    "    }\n",
    "]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:34:49.456530Z",
     "start_time": "2023-07-28T09:34:49.449096Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "outputs": [],
   "source": [
    "def ask_database(conn, query):\n",
    "    \"\"\"使用 query 来查询 SQLite 数据库的函数。\"\"\"\n",
    "    try:\n",
    "        results = str(conn.execute(query).fetchall())  # 执行查询，并将结果转换为字符串\n",
    "    except Exception as e:  # 如果查询失败，捕获异常并返回错误信息\n",
    "        results = f\"query failed with error: {e}\"\n",
    "    return results  # 返回查询结果\n",
    "\n",
    "\n",
    "def execute_function_call(message):\n",
    "    \"\"\"执行函数调用\"\"\"\n",
    "    # 判断功能调用的名称是否为 \"ask_database\"\n",
    "    if message[\"function_call\"][\"name\"] == \"ask_database\":\n",
    "        # 如果是，则获取功能调用的参数，这里是 SQL 查询\n",
    "        query = json.loads(message[\"function_call\"][\"arguments\"])[\"query\"]\n",
    "        # 使用 ask_database 函数执行查询，并获取结果\n",
    "        results = ask_database(conn, query)\n",
    "    else:\n",
    "        # 如果功能调用的名称不是 \"ask_database\"，则返回错误信息\n",
    "        results = f\"Error: function {message['function_call']['name']} does not exist\"\n",
    "    return results  # 返回结果"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:34:52.922912Z",
     "start_time": "2023-07-28T09:34:52.916986Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Answer user questions by generating SQL queries against the Chinook Music Database.\n",
      "\u001B[0m\n",
      "\u001B[32muser: Hi, who are the top 5 artists by number of tracks?\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"ask_database\",\n",
      "  \"arguments\": \"{\\n  \\\"query\\\": \\\"SELECT artists.Name, COUNT(tracks.TrackId) AS TrackCount FROM artists JOIN albums ON artists.ArtistId = albums.ArtistId JOIN tracks ON albums.AlbumId = tracks.AlbumId GROUP BY artists.ArtistId ORDER BY TrackCount DESC LIMIT 5\\\"\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (ask_database): [('Iron Maiden', 213), ('U2', 135), ('Led Zeppelin', 114), ('Metallica', 112), ('Deep Purple', 92)]\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 创建一个空的消息列表\n",
    "messages = []\n",
    "\n",
    "# 向消息列表中添加一个系统角色的消息，内容是 \"Answer user questions by generating SQL queries against the Chinook Music Database.\"\n",
    "messages.append({\"role\": \"system\",\n",
    "                 \"content\": \"Answer user questions by generating SQL queries against the Chinook Music Database.\"})\n",
    "\n",
    "# 向消息列表中添加一个用户角色的消息，内容是 \"Hi, who are the top 5 artists by number of tracks?\"\n",
    "messages.append({\"role\": \"user\", \"content\": \"Hi, who are the top 5 artists by number of tracks?\"})\n",
    "\n",
    "# 使用 chat_completion_request 函数获取聊天响应\n",
    "assistant_message = chat_completion_api(messages, functions)\n",
    "\n",
    "# 将助手的消息添加到消息列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 如果助手的消息中有功能调用\n",
    "if assistant_message.get(\"function_call\"):\n",
    "    # 使用 execute_function_call 函数执行功能调用，并获取结果\n",
    "    results = execute_function_call(assistant_message)\n",
    "    # 将功能的结果作为一个功能角色的消息添加到消息列表中\n",
    "    messages.append({\"role\": \"function\", \"name\": assistant_message[\"function_call\"][\"name\"], \"content\": results})\n",
    "\n",
    "# 使用 pretty_print_conversation 函数打印对话\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:35:03.699545Z",
     "start_time": "2023-07-28T09:34:55.757879Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001B[31msystem: Answer user questions by generating SQL queries against the Chinook Music Database.\n",
      "\u001B[0m\n",
      "\u001B[32muser: Hi, who are the top 5 artists by number of tracks?\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"ask_database\",\n",
      "  \"arguments\": \"{\\n  \\\"query\\\": \\\"SELECT artists.Name, COUNT(tracks.TrackId) AS TrackCount FROM artists JOIN albums ON artists.ArtistId = albums.ArtistId JOIN tracks ON albums.AlbumId = tracks.AlbumId GROUP BY artists.ArtistId ORDER BY TrackCount DESC LIMIT 5\\\"\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (ask_database): [('Iron Maiden', 213), ('U2', 135), ('Led Zeppelin', 114), ('Metallica', 112), ('Deep Purple', 92)]\n",
      "\u001B[0m\n",
      "\u001B[32muser: What is the name of the album with the most tracks?\n",
      "\u001B[0m\n",
      "\u001B[34massistant[function_call]: {\n",
      "  \"name\": \"ask_database\",\n",
      "  \"arguments\": \"{\\n  \\\"query\\\": \\\"SELECT albums.Title, COUNT(tracks.TrackId) AS TrackCount FROM albums JOIN tracks ON albums.AlbumId = tracks.AlbumId GROUP BY albums.AlbumId ORDER BY TrackCount DESC LIMIT 1\\\"\\n}\"\n",
      "}\n",
      "\u001B[0m\n",
      "\u001B[35mfunction (ask_database): [('Greatest Hits', 57)]\n",
      "\u001B[0m\n"
     ]
    }
   ],
   "source": [
    "# 向消息列表中添加一个用户的问题，内容是 \"What is the name of the album with the most tracks?\"\n",
    "messages.append({\"role\": \"user\", \"content\": \"What is the name of the album with the most tracks?\"})\n",
    "\n",
    "# 使用 chat_completion_request 函数获取聊天响应\n",
    "assistant_message = chat_completion_api(messages, functions)\n",
    "\n",
    "# 将助手的消息添加到消息列表中\n",
    "messages.append(assistant_message)\n",
    "\n",
    "# 如果助手的消息中有功能调用\n",
    "if assistant_message.get(\"function_call\"):\n",
    "    # 使用 execute_function_call 函数执行功能调用，并获取结果\n",
    "    results = execute_function_call(assistant_message)\n",
    "    # 将功能的结果作为一个功能角色的消息添加到消息列表中\n",
    "    messages.append({\"role\": \"function\", \"content\": results, \"name\": assistant_message[\"function_call\"][\"name\"]})\n",
    "\n",
    "# 使用 pretty_print_conversation 函数打印对话\n",
    "pretty_print_conversation(messages)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-07-28T09:35:11.108399Z",
     "start_time": "2023-07-28T09:35:07.030142Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
